Software developers create software using one or more programming languages. Programming languages are usually either statically typed languages, or they are dynamic languages. Statically typed languages are generally languages that provide a fixed code structure and/or static typing. With a fixed code structure, classes and other structures are immutable at runtime, meaning that their structure cannot be modified after the class or structure is created. With static typing, variables and parameters must be assigned at design time so that they can be known at compile time. Statically typed languages can include some or all of these characteristics. Some examples of statically typed languages include C#, Java, C, and C++.
Dynamic languages are generally languages that provide dynamic variable typing and/or runtime code modification. With dynamic variable typing, variables do not have to be declared before use, and their type (integer, string, etc.) is thus not determined until runtime. Languages that implement dynamic typing are often referred to as dynamically typed languages (in addition to the term dynamic languages). With runtime code modification, classes and other details about the structure of the source code can be modified dynamically. For example, methods may be added to classes or structures at runtime. Some examples of dynamic languages include JavaScript, Ruby, Python, and PHP. These languages are often referred to as “scripting” languages because of the fact that their source code is contained in one or more textual script files. The term “dynamic language script” as used herein is meant to include one or more files that contain source code written in a dynamic language.
These features typically provided by dynamic languages offer software developers with a lot of flexibility. As an example, dynamic languages can be used to build objects from scratch or extend existing objects at runtime. The term “object” as used herein is referring to objects in the object oriented world of programming, and is meant to include an individual unit of runtime data storage that is used as the basic building block of a program. Objects can optionally have methods (functions, procedures, etc.) associated with them that can be run within the context of the unit of runtime data storage. While dynamic languages offer flexibility, with this flexibility comes some problems. For example, a well defined type is a lot more difficult to determine since the specific type that a variable will be used for does not have to be declared in advance. Thus, the program does not know whether a variable called counter is an integer, a string, a double, or something else. This can make parameter validation and other validations problematic, because any verification has to make assumptions about what a parameter object can or cannot do.
The feature known as “type safety”, usually associated with statically typed languages, seems to conflict with the spirit of dynamic languages. The term type safety generally refers to determining what type an object or variable represents, and ensuring that the object or variable is only used in a manner consistent with the type expected. For example, if a line of code attempts to assign a variable that stores an integer value to a variable that stores a string value, the compiler could raise a compiler error based upon a type mismatch. That is because the integer variable is not capable of storing the value represented in the string variable.
As noted earlier, in dynamically typed languages, such type safety is not present. This problem is usually solved using a technique known as “duck-typing”, which uses the reflection capabilities that dynamic languages typically possess to inspect the object and check for a particular member's existence before using it. This is often called “duck-typing” based on the saying “if it quacks like a duck, you can consider it to be a duck”. While the duck-typing technique can work well in common scenarios, it presents a number of challenges.
One problem with duck typing is that there is not a one to one mapping between a member (variable, object, etc.) name and the semantics of that member. The context is usually necessary to get this one to one mapping. This context, in statically typed languages, is the type. For example, if an object has a start method, it could have been written to start an animation, which would be clear if it is known that the object is an animation. Alternatively, the object could have been written to start a timer, which is clear if it is known that the object is a timer. This works well if the context can be easily inferred, such as if the code that creates the object is the same code that uses it. But this scenario fails when the context is not available. For example, suppose the program is inspecting an unknown object graph and wants to start all animations in it. By relying on the presence of a start method only, then all timers would be started, which is not the intent. Using the hypothetical example with statically typed languages, you can just check for the type of an object, and if it derives from Animation, you can call the start method on the Animation object with reasonable confidence that what you intend is what will happen.
Another problem with duck-typing is that it hides important details within the code. For example, in dynamic languages, you typically have to look at the implementation (the underlying source code of the particular object or method) to figure out what constraints are going to be imposed on parameters. This problem is usually solved by putting the burden on documentation. However, this part of documentation is usually automatically generated from method signatures in statically typed languages. This is because in statically typed languages, the signature of a method generally identifies everything needed in order to call it. Thus, dynamic languages are losing at least part of the boost of productivity provided by dynamic typing by requiring manual authoring of documentation that could be generated.
In statically typed languages, these problems do not typically exist because objects are instances of one or several types and implement one or more contracts, usually in the way of interfaces. The term “contract” means a specified agreement that any object or other programming structure that is providing an implementation for a given functionality agrees to implement. Interfaces are the most common way for providing a definition for a contract. Interfaces require that any object (or other member) that wishes to implement the interface provide a specific implementation for all of the members that are defined in the interface. Some non-limiting examples of members include properties, methods, events, and/or fields. The interface itself does not contain any implementation of the functionality, but rather just the definition of what the implementation should contain. There are also other ways to specify a contract, such as by specifying an abstract base class that other classes can inherit from, or by using other techniques for specifying what features certain objects or other members must implement.
Contracts and interfaces are very useful, but as a practical matter, they usually do not exist at all in dynamic languages. Interfaces (or other contracts) make a lot less sense when objects can be built from scratch, such as with dynamic languages. The root of that problem is that interfaces are implemented by types. As previously discussed, statically typed languages typically provide type safe members with types that are known at compile time, whereas dynamic languages do not. Thus, with statically typed languages, the interface is associated with a type, and the type is then associated with the underlying object. Thus, the relationship between the interface and the object it is associated with is an indirect relationship that is established through the type itself. The lack of type safety is one reason why interfaces are not typically used in dynamic languages as they are in statically typed languages.
On a separate but related note, some dynamic languages use a generic way of adding a specific set of features to an object dynamically, but under the form of a module or mix-in. More specifically, a mix-in is one or more member implementations that can be added dynamically to an object (the target). The term “target object” will be used herein to refer to an object to which a mix-in is to be applied. Mix-ins are a concept similar to multiple inheritance, but for dynamic languages. A mix-in can contain methods that can be added in one operation to an object, such as with an equivalent to an include statement that refers to the mix-in group of code. From the user code's perspective, it is still necessary in principle to test for the existence of each method before using it. There is also a small possibility that an object may appear to provide certain functionality but be something entirely different. Furthermore, members defined in the mix-in may conflict with and overwrite existing members of the object.